﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Net.Chirash.Twitter.TwitterDatabase;
using System.Data;
using System.Data.Objects;
using System.Data.SqlServerCe;

namespace Net.Chirash.Twitter.TwitterClient
{
    public class TwitterLocalCommand
        : TwitterCaching, ITwitterCommand
    {
        public TwitterLocalCommand(TwitterCachingConnection conn, User currentUser)
            : base(conn)
        {
            this.CurrentUser = currentUser;
            //conn.Entities.Statuses.MergeOption = System.Data.Objects.MergeOption.NoTracking;
            //conn.Entities.Users.MergeOption = System.Data.Objects.MergeOption.NoTracking;
            //conn.Entities.DirectMessages.MergeOption = System.Data.Objects.MergeOption.NoTracking;
        }

        public TwitterLocalCommand(TwitterCachingConnection conn)
            : this(conn, null)
        { }

        public new Timeline DestroyStatus(long statusID)
        {
            base.DestroyStatus(statusID);
            return new Timeline();
        }

        public Timeline Favorites(User user)
        {
            throw new NotImplementedException();
        }

        public Timeline Favorites(User user, int page)
        {
            throw new NotImplementedException();
        }

        public Timeline Favorites(long userID)
        {
            throw new NotImplementedException();
        }

        public UserList Followers(UserListOption option)
        {
            UserList result = null;
            UseEntity((context) =>
            {
                var followers = ExtractFollowers(option, context.Users);
                if (followers != null)
                    result = new UserList(followers);

            }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        public IEnumerable<User> ExtractFollowers(UserListOption option, IQueryable<User> users)
        {
            var q = from user in users
                    where user.UserID == option.User.UserID
                    select user.Followers;
            if (q != null && q.Any())
                return q.First().ToArray();
            else
                return null;
        }

        public UserList Friends(UserListOption option)
        {
            UserList result = null;
            UseEntity((context) =>
            {
                var friends = ExtractFriends(option, context.Users);
                if (friends != null)
                    result = new UserList(friends);

            }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        public IEnumerable<User> ExtractFriends(UserListOption option, IQueryable<User> users)
        {
            var q = from user in users
                    where user.UserID == option.User.UserID
                    select user.Friends;
            if (q != null && q.Any())
                return q.First().ToArray();
            else
                return null;
        }

        public DirectMessageCollection GetDirectMessages(DirectMessageOption option)
        {
            if (this.CurrentUser == null)
                throw new InvalidOperationException("CurrentUserが指定されないままDirectMessageを取得しようとしました");

            DirectMessageCollection result = null;
            UseEntity((context) =>
                {
                    var q = ExtractDirectMessages(option, context.DirectMessages.Include("Sender").Include("Recipient"));
                    result = new DirectMessageCollection(q);

                }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        public IEnumerable<DirectMessage> ExtractDirectMessages(DirectMessageOption option, IQueryable<DirectMessage> directmessages)
        {
            var q = from dm in directmessages
                    where dm.SenderID == this.CurrentUser.UserID
                         && dm.DirectMessageID > option.SinceID
                         && dm.DirectMessageID <= ((option.MaxID == 0) ? long.MaxValue : option.MaxID)
                    orderby dm.DirectMessageID descending
                    select dm;
#if DEBUG
            Console.WriteLine(ToQueryString<DirectMessage>(q.AsQueryable<DirectMessage>()));
#endif
            return q.Take(((option.Count == 0) ? 20 : option.Count)).ToArray();
        }

        public Timeline GetFriendsTimeline(TimelineOption option)
        {
            Timeline result = null;
            UseEntity((context) =>
                {
                    var friends = ExtractFriendsTimeline(option, context.Statuses.Include("User"), context.Users);
                    if (friends != null)
                        result = new Timeline(friends);
                    else
                        result = null;

                }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        public IEnumerable<Status> ExtractFriendsTimeline(TimelineOption option, IQueryable<Status> statuses, IQueryable<User> users)
        {
            var q = from sta in statuses
                    from friend in users
                    from currentUser in users
                    where currentUser.UserID == this.CurrentUser.UserID
                        && sta.StatusID > option.SinceID
                        && sta.StatusID <= ((option.MaxID == 0) ? long.MaxValue : option.MaxID)
                        && currentUser.Friends.Contains(friend)
                        && (sta.UserID == friend.UserID)
                    orderby sta.StatusID descending
                    select sta;
#if DEBUG
            Console.WriteLine(ToQueryString<Status>(q.AsQueryable<Status>()));
#endif
            return q.Take(((option.Count == 0) ? 20 : option.Count)).ToArray();
        }

        public Timeline GetHomeTimeline(TimelineOption option)
        {
            Timeline result = null;
            UseEntity((context) =>
                {
                    var cmdstr = @"select distinct top ({0}) sta.*
                                    from Statuses as sta
                                    inner join fk_Friends_Followers as follower
                                    on follower.Followers_UserID = {1}
	                                    and (sta.UserID = follower.Friends_UserID
	                                    or sta.UserID = {1})	
                                    where sta.StatusID > {2} and sta.statusID <= {3}
	                                    and (
		                                    sta.InReplyToUserID is null
		                                    or sta.UserID = {1}
                                            or sta.InReplyToUserID = {1}
		                                    or  exists(select *
				                                    from fk_Friends_Followers as reply
				                                    where reply.Followers_UserID = {1}
					                                    and reply.Friends_UserID = sta.InReplyToUserID
			                                    )
		                                    )
                                    order by sta.StatusID desc";
                    var statuses = context.ExecuteStoreQuery<Status>(cmdstr,
                                                                    "Statuses",
                                                                    MergeOption.NoTracking,
                                                                    ((option.Count == 0) ? 20 : option.Count),
                                                                    option.UserID,
                                                                    option.SinceID,
                                                                    ((option.MaxID == 0) ? long.MaxValue : option.MaxID)).ToArray();
                    var buf = statuses.ToArray();
                    foreach(var sta in buf)
                        context.LoadProperty<Status>(sta, (item) => item.User);

                    result = new Timeline(buf);
                }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        //public IEnumerable<Status> ExtractHomeTimeline(TimelineOption option, IQueryable<Status> statuses, IQueryable<User> users)
        //{
        //    var q = from sta in statuses
        //            from friend in users
        //            from currentUser in users
        //            where currentUser.UserID == this.CurrentUser.UserID
        //                && currentUser.Friends.Contains(friend)
        //                && sta.StatusID > option.SinceID
        //                && sta.StatusID <= ((option.MaxID == 0) ? long.MaxValue : option.MaxID)
        //                && (
        //                    sta.UserID == currentUser.UserID
        //                    || (sta.UserID == friend.UserID && (sta.InReplyToUserID == null || currentUser.Friends.Contains(sta.InReplyToUser)))
        //                   )
        //            orderby sta.StatusID descending
        //            select sta;

        //    return q.Take(((option.Count == 0) ? 20 : option.Count));

        //}

        public Timeline GetMentionsTimeline(TimelineOption option)
        {
            Timeline result = null;
            UseEntity((context) =>
                {
                    result = new Timeline(ExtractMentionsTimeline(option, context.Statuses.Include("User")));

                }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        public IEnumerable<Status> ExtractMentionsTimeline(TimelineOption option, IQueryable<Status> statuses)
        {
            var q = from sta in statuses
                    where
                        sta.StatusID > option.SinceID
                        && sta.StatusID <= ((option.MaxID == 0) ? long.MaxValue : option.MaxID)
                        &&
                        (
                            sta.InReplyToUserID == this.CurrentUser.UserID
                        )
                    orderby sta.StatusID descending
                    select sta;
#if DEBUG
            Console.WriteLine(ToQueryString<Status>(q.AsQueryable<Status>()));
#endif
            return q.Take(((option.Count == 0) ? 20 : option.Count)).ToArray();
        }

        public Timeline GetPublicTimeline()
        {
            return new Timeline(this.GetAllStatus());
        }

        public Timeline GetRepliesTimeline(TimelineOption option)
        {
            return GetMentionsTimeline(option);
        }

        public Timeline GetRetweetedByMeTimeline(TimelineOption option)
        {
            throw new NotImplementedException();
        }

        public Timeline GetRetweetedOfMeTimeline(TimelineOption option)
        {
            throw new NotImplementedException();
        }

        public Timeline GetRetweetedToMeTimeline(TimelineOption option)
        {
            throw new NotImplementedException();
        }

        public DirectMessageCollection GetSentDirectMessages(DirectMessageOption option)
        {
            DirectMessageCollection result = null;
            UseEntity((context) =>
                {
                    result = new DirectMessageCollection(ExtractSentDirectMessages(option, context.DirectMessages.Include("Sender").Include("Recipient")));

                }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        public IEnumerable<DirectMessage> ExtractSentDirectMessages(DirectMessageOption option, IQueryable<DirectMessage> dms)
        {
            var q = from dm in dms
                    where dm.DirectMessageID > option.SinceID
                        && dm.DirectMessageID <= ((option.MaxID == 0) ? long.MaxValue : option.MaxID)
                        && dm.SenderID == this.CurrentUser.UserID
                    orderby dm.DirectMessageID descending
                    select dm;
#if DEBUG
            Console.WriteLine(ToQueryString<DirectMessage>(q.AsQueryable<DirectMessage>()));
#endif
            return q.Take(((option.Count == 0) ? 20 : option.Count)).ToArray();
        }

        public Timeline GetUserTimeline(TimelineOption option)
        {
            Timeline result = null;
            UseEntity((context) =>
            {
                result = new Timeline(ExtractUserTimeline(option, context.Statuses.Include("User")));

            }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        public IEnumerable<Status> ExtractUserTimeline(TimelineOption option, IQueryable<Status> statuses)
        {
            var q = from sta in statuses
                    where sta.StatusID > option.SinceID
                        && sta.StatusID <= ((option.MaxID == 0) ? long.MaxValue : option.MaxID)
                        && sta.UserID == option.UserID
                    orderby sta.StatusID descending
                    select sta;
#if DEBUG
            Console.WriteLine(ToQueryString<Status>(q.AsQueryable<Status>()));
#endif
            return q.Take(((option.Count == 0) ? 20 : option.Count)).ToArray();
        }

        public UserList SearchUser(string keyword)
        {
            throw new NotImplementedException();
        }

        public Status ShowStatus(long statusID)
        {
            return this.GetStatus(statusID);
        }

        public User ShowUser(long userID)
        {
            return this.GetUser(userID);
        }

        public User ShowUser(string screenName)
        {
            User result = null;
            UseEntity((context) =>
                {
                    var q = from user in context.Users
                            where user.ScreenName == screenName
                            select user;
                    if (q != null && q.Any())
                        result = q.First();

                }, true, IsolationLevel.ReadCommitted);
            return result;
        }

        public User CurrentUser
        {
            get;
            private set;
        }

        public void RefreshCurrentUser(ITwitterCommand sourceCommand)
        {
            var user = sourceCommand.ShowUser(this.CurrentUser.UserID);
            var friends = getUserRecursively(new UserListOption() { User = user, Lite = true },
                                             new List<User>(),
                                             -1,
                                             (option) => sourceCommand.Friends(option)
                                             );

            var followers = getUserRecursively(new UserListOption() { User = user, Lite = true },
                                             new List<User>(),
                                             -1,
                                             (option) => sourceCommand.Followers(option)
                                             );

            this.UpdateUsers(friends.Concat(followers).Concat(new User[] { user }).Distinct());
            this.UpdateUserFriends(user.UserID, friends);
            this.UpdateUserFollowers(user.UserID, followers);

            this.CurrentUser = this.ShowUser(user.UserID);
        }

        private List<User> getUserRecursively(UserListOption option, List<User> list, long cursor, Func<UserListOption, UserList> act)
        {
            option.Cursor = cursor;
            var buf = act(option);
            list.AddRange(buf);
            if (buf.NextCursor != 0)
                getUserRecursively(option, list, buf.NextCursor, act);

            return list;
        }

#if DEBUG
        private static string ToQueryString<T>(IQueryable<T> q)
        {
            var qobj = q as ObjectQuery<T>;
            if (qobj != null)
                return qobj.ToTraceString();
            else
                return "cannot cast to ObjectQuery";
        }
#endif
    }
}
